package xfuzzy.xfghl.cross;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import xfuzzy.xfghl.codification.ChromosomeFixer;
import xfuzzy.xfghl.codification.DoubleChromosome;
import xfuzzy.xfghl.codification.Encoder;
import xfuzzy.xfghl.model.XfghlConfig;
import xfuzzy.xfhl.model.hierStruct.XfhlINode;
import xfuzzy.xfhl.model.hierStruct.XfhlModule;
import xfuzzy.xfhl.model.hierStruct.XfhlNode;

/**
 * Clase encargada de cruzar cromosomas mediante tecnicas de programacion genetica
 * @author Alberto David Fernandez Fernandez
 * */

public class GPCrossover  {
	
	/**
	 * Variable para la generacion de numeros aleatorios
	 * */
	private Random random;
	
	/**
	 * Constructor de la clase
	 * @param seed Semilla del generador de numeros aleatorios
	 * */
	
	public GPCrossover(long seed)  {
		
		this.random = new Random(seed);
	}
	
	/**
	 * Metodo que cruza dos cromosomas
	 * @param padre1 Cromosoma a cruzar
	 * @param padre2 Cromosoma a cruzar
	 * @param config Configuracion de la herramienta Xfghl
	 * @return List<DoubleChromosome> Lista con la descendencia generada
	 * */

	public List<DoubleChromosome> cruzar(DoubleChromosome padre1, DoubleChromosome padre2, XfghlConfig config) {

		DoubleChromosome child1 = null;
		DoubleChromosome child2 = null;
		List<DoubleChromosome> childs = new ArrayList<DoubleChromosome>();
		double probRandom = this.random.nextDouble();
		int variablesPadre1 = -1, variablesPadre2 = -1;
		// Se construyen las estructuras jerarquicas de ambos cromosomas
		XfhlModule moduloPadre1 = Encoder.decodeBackwardAnchuraSimple(padre1);
		XfhlModule moduloPadre2 = Encoder.decodeBackwardAnchuraSimple(padre2);
		double [] prob = new double[padre1.getNumModulos()];
		int indice = 0;
		XfhlModule actual = null;
		XfhlModule seleccionadoPadre1 = null;
		XfhlModule seleccionadoPadre2 = null;
		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
		modulos.add(moduloPadre1);
		// se guardan en una lista todos los modulos de la estructura jerarquica padre1
		while (indice < modulos.size())  {
			actual = modulos.get(indice);
			if (actual.getEnt2() instanceof XfhlModule)  {
				modulos.add((XfhlModule) actual.getEnt2());
			}
			if (actual.getEnt1() instanceof XfhlModule)  {
				modulos.add((XfhlModule) actual.getEnt1());
			}
			// comprobamos que si es un nodo terminal se le de menos probabilidad
			if ((actual.getEnt2() instanceof XfhlNode) && (actual.getEnt1() instanceof XfhlNode))  {
				prob[indice] = 1.0 / padre1.getNumModulos();
			}
			else  
				prob[indice] = 1.0;
			indice++;
		}
		while (seleccionadoPadre2 == null)  {
			// se elige un modulo del padre 1 aleatoriamente (los primeros modulos tienen mas probabilidad)
			int indiceRandom = this.random.nextInt(modulos.size());
			while (probRandom > prob[indiceRandom])  {
				indiceRandom = this.random.nextInt(modulos.size());
			}
			seleccionadoPadre1 = modulos.get(indiceRandom);
			// se cuenta el numero de variables que dependen de este modulo
			variablesPadre1 = this.cuentaVariablesRama(seleccionadoPadre1);
			modulos = new ArrayList<XfhlModule>();
			modulos.add(moduloPadre2);
			indice = 0;
			// se guardan en una lista todos los modulos de la estructurar jerarquica padre2
			while (indice < modulos.size())  {
				actual = modulos.get(indice);
				if (actual.getEnt2() instanceof XfhlModule)  {
					modulos.add((XfhlModule) actual.getEnt2());
				}
				if (actual.getEnt1() instanceof XfhlModule)  {
					modulos.add((XfhlModule) actual.getEnt1());
				}
				indice++;
			}
			// se busca el modulo que contenga el mismo numero de variables
			for (int indicePadre2 = 0; indicePadre2 < modulos.size(); indicePadre2++)  {
				 actual = modulos.get(indicePadre2);
				 variablesPadre2 = this.cuentaVariablesRama(actual);
				 if (variablesPadre1 == variablesPadre2)  {
					 seleccionadoPadre2 = actual;
					 break;
				 }
			}
		}
		// se intercambian los modulos seleccionados
		XfhlINode auxiliarEntrada1 = null;
		XfhlINode auxiliarEntrada2 = null;
		auxiliarEntrada1 = seleccionadoPadre1.getEnt1();
		auxiliarEntrada2 = seleccionadoPadre1.getEnt2();
		seleccionadoPadre1.setEnt1(seleccionadoPadre2.getEnt1());
		seleccionadoPadre1.setEnt2(seleccionadoPadre2.getEnt2());
		seleccionadoPadre2.setEnt1(auxiliarEntrada1);
		seleccionadoPadre2.setEnt2(auxiliarEntrada2);
		// se codifican los cromosomas a partir de las estructuras jerarquicas generadas
		child1 = Encoder.codeBackwardAnchura(moduloPadre1, padre1.getNumVariables());
		child2 = Encoder.codeBackwardAnchura(moduloPadre2, padre2.getNumVariables());
		child1.setEvaluated(false);
		child2.setEvaluated(false);
		childs.add(child1);
		childs.add(child2);
		// se comprueba que los cromosomas creados sean correctos
		ChromosomeFixer.reparar(child1, config);
		ChromosomeFixer.reparar(child2, config);
		return childs;
	}
	
	/**
	 * Metodo que cuenta el numero de variables de un modulo
	 * @param modulo Modulo que contiene las entradas
	 * @return int Valor con el numero de variables del modulo
	 * */

	private int cuentaVariablesRama(XfhlModule modulo)  {
		
		int variablesRama = 0;
		List<XfhlModule> modulosPendientes = new ArrayList<XfhlModule>();
		XfhlModule actual = null;
		modulosPendientes.add(modulo);
		while (modulosPendientes.size() > 0)  {
			actual = modulosPendientes.remove(0);
			if (actual.getEnt2() instanceof XfhlModule)  {
				modulosPendientes.add((XfhlModule) actual.getEnt2());
			}
			if (actual.getEnt1() instanceof XfhlModule)  {
				modulosPendientes.add((XfhlModule) actual.getEnt1());
			}
			if (actual.getEnt2() instanceof XfhlNode)  {
				variablesRama++;
			}
			if (actual.getEnt1() instanceof XfhlNode)  {
				variablesRama++;
			}
		}
		return variablesRama;
	}
}
